package nl.rnplus.olv.service;

import java.io.IOException;
import java.util.UUID;

import nl.rnplus.olv.LiveViewPreferences;
import nl.rnplus.olv.MainActivity;
import nl.rnplus.olv.R;
import nl.rnplus.olv.data.LiveViewDbConstants;
import nl.rnplus.olv.data.LiveViewDbHelper;
import nl.rnplus.olv.messages.MessageConstants;
import nl.rnplus.olv.messages.calls.CapsRequest;
import nl.rnplus.olv.messages.calls.SetLed;
import nl.rnplus.olv.messages.calls.SetScreenMode;
import nl.rnplus.olv.messages.calls.SetVibrate;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.ContactsContract;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;

/**
 * This service hosts and controls the thread communicating with the LiveView
 * device.
 **/
public class LiveViewService extends Service
{
	private static final String TAG = "LiveViewService";
    
    private static final UUID SERIAL = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    private static final int SERVICE_NOTIFY = 100;

    /* Communication */
    private LiveViewSendThread sendThread = null;
    private LiveViewThread workerThread = null;
    private BluetoothAdapter btAdapter;
    private BluetoothServerSocket serverSocket;
    private long startUpTime;
    private BluetoothSocket clientSocket;

    private Notification notification;
    
    BroadcastReceiver plugin_receiver = new PluginCommandReceiver();
    MyPhoneStateListener phoneListener = new MyPhoneStateListener();

    /*
     * (non-Javadoc)
     * @see android.app.Service#onBind(android.content.Intent)
     */
    @Override
    public IBinder onBind(Intent intent)
    {
        return null;
    }
    
    @Override
    public void onCreate()
    {    
    	registerReceiver(plugin_receiver, new IntentFilter(PLUGIN_COMMAND));

    	IntentFilter mediaintentfilter = new IntentFilter();
    	mediaintentfilter.addAction("com.android.music.metachanged");
    	mediaintentfilter.addAction("com.android.music.playstatechanged");
    	mediaintentfilter.addAction("com.android.music.playbackcomplete");
    	mediaintentfilter.addAction("com.android.music.queuechanged");
    	registerReceiver(media_receiver, mediaintentfilter);
    	
    	TelephonyManager telephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
    	telephonyManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
    }
    
    @Override
    public void onDestroy()
    {    
    	unregisterReceiver(media_receiver);
    	unregisterReceiver(plugin_receiver);    	
    }    

    public static final	String ACTION_START = "start";
    public static final String ACTION_STOP = "stop";

    /*
     * (non-Javadoc)
     * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        String action = intent.getAction();
        if (ACTION_START.equals(action))
        {
            start();
        } else if (ACTION_STOP.equals(action))
        {
            stop();
        }
        return START_NOT_STICKY;
    }

    /**
     * Starts the worker thread if the thread is not already running. If there
     * is a thread running that has already been stopped then a new thread is
     * started.
     */
    @SuppressWarnings("deprecation") //To support Android 2.x the depricated notification method is still used on low pre-JellyBean devices.
    private void start()
    {
        int sdk = Build.VERSION.SDK_INT;

        //Pending intent 1: Open the mainActivity
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pi_content = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        //Pending intent 2: Find the LiveView
        Intent bci = new Intent(PLUGIN_COMMAND);
        Bundle bcb = new Bundle();
        bcb.putString("command", "vibrate");
        bcb.putInt("delay", 0);
        bcb.putInt("time", 1000);
        long time = System.currentTimeMillis();
        bcb.putLong("timestamp", time);
        bci.putExtras(bcb);
        PendingIntent pi_findliveview = PendingIntent.getBroadcast(this, 0, bci, 0);

        //Pending intent 3: Open the settings
        Intent i_opensettings = new Intent();
        i_opensettings.setClass(this, LiveViewPreferences.class);
        PendingIntent pi_opensettings = PendingIntent.getActivity(this, 0, i_opensettings, 0);

        if (sdk < Build.VERSION_CODES.JELLY_BEAN) //Use deprecated notification code when running on android versions older than Jelly Bean.
        {
            notification = new Notification(R.drawable.icon, "LiveView connected...", System.currentTimeMillis());
            Context context = this.getApplicationContext();
            CharSequence contentTitle = this.getString(R.string.app_name);
            CharSequence contentText = this.getString(R.string.notify_service_running);
            notification.setLatestEventInfo(context, contentTitle, contentText, pi_content);
        } else {
            CharSequence contentTitle = this.getString(R.string.app_name);
            CharSequence contentText = this.getString(R.string.notify_service_running);
            //Bitmap icon = BitmapFactory.decodeResource(parentService.getResources(), R.drawable.olv_icon);
            notification = new Notification.Builder(this.getApplicationContext())
                    .setContentTitle(contentTitle)
                    .setContentText(contentText)
                    .setSmallIcon(R.drawable.ic_liveview)
                    .setContentIntent(pi_content)
                    .addAction(R.drawable.ic_menu_find, this.getString(R.string.notification_findliveview), pi_findliveview)
                    .addAction(R.drawable.ic_menu_manage, this.getString(R.string.notification_settings), pi_opensettings)
                    .setOngoing(true)
					.setWhen(0)
					.setPriority(Notification.PRIORITY_LOW)
                    .build();
        }

        startForeground(SERVICE_NOTIFY, notification);

        Log.d(TAG, "Starting LiveView threads.");

        startUpTime = System.currentTimeMillis();
        serverSocket = null;
        try {
            Log.d(TAG, "Starting server...");
            btAdapter = BluetoothAdapter.getDefaultAdapter();
            serverSocket = btAdapter.listenUsingRfcommWithServiceRecord("LiveView", SERIAL);
        } catch (IOException e) {
            Log.e(TAG, "Error starting BT server: " + e.getMessage());
            return;
        }

        try {
            Log.d(TAG, "Listening for LV...");
            BluetoothSocket clientSocket = serverSocket.accept();
            sendThread = new LiveViewSendThread(this, clientSocket);
            workerThread = new LiveViewThread(this, clientSocket.getInputStream(), sendThread);
            
            sendThread.start();
            workerThread.start();
            
            // Single connect only
            serverSocket.close();
            serverSocket = null;
            Log.d(TAG, "LV connected.");
            workerThread.sendCall(new CapsRequest());
            Log.d(TAG, "Message sent.");
            sendEvent("connectionstatus".toString(), 1, 0, "connected".toString(), null);
	    } catch (IOException e) {
	        String msg = e.getMessage();
	        if (msg.contains("Connection timed out")) {
	            Log.e(TAG, "Timed out communicating with LV: " + e.getMessage());
	        } else {
	            Log.e(TAG, "Error communicating with LV: " + e.getMessage());
	        }
	    }
    }

    /**
     * Signals the current worker thread to stop itself. If no worker thread is
     * available then nothing is done.
     */
    void stop()
    {
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                String message = "Error while closing server socket: " + e.getMessage();
                Log.e(TAG, message);
            }
            serverSocket = null;
        }

        if (clientSocket != null) {
            try {
            	clientSocket.close();
            } catch (IOException e) {
                String message = "Error while closing client socket: " + e.getMessage();
                Log.e(TAG, message);
            }

            clientSocket = null;
        }

        if (sendThread != null)
        {
        	if (sendThread.isAlive())
        		sendThread.interrupt();

        	sendThread = null;
        }

        if (workerThread != null)
        {
        	if (workerThread.isAlive())
        		workerThread.interrupt();

        	workerThread = null;
        }

        if (startUpTime > 0)
        {
	        // Log runtime
	        long runtime = (System.currentTimeMillis() - startUpTime) / 1000;
	        long runHour = runtime / 3600;
	        runtime -= runHour * 3600;
	        long runMinute = runtime / 60;
	        runtime -= runMinute * 60;
	        String message = String.format("Service runtime: %d hours %d minutes %d seconds", runHour, runMinute, runtime);
	        Log.d(TAG, message);
        }

        startUpTime = 0;

        // Stop surrounding service
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(SERVICE_NOTIFY);
        stopForeground(true);
        stopSelf();
    }
        
    /* Notifications */

    public String getNotificationContent(int notification, int type)
    {
    	String content = "ERROR";
    	try {
	    	int id = 0;
	    	LiveViewDbHelper dbHelper;
	    	Cursor cursor;
			dbHelper = new LiveViewDbHelper(this);	
			dbHelper.openToRead();
			cursor = dbHelper.getAlertsOfType(type);
			int index_CONTENT = cursor.getColumnIndex(LiveViewDbConstants.COLUMN_ALERT_ITEMS_CONTENT);
			int index_ID = cursor.getColumnIndex(LiveViewDbConstants.COLUMN_ALERT_ITEMS_ID);
			cursor.moveToPosition(notification);
			content = cursor.getString(index_CONTENT);
			id = cursor.getInt(index_ID);
			cursor.close();
			dbHelper.close();
			dbHelper.openToWrite();
			dbHelper.setAlertRead(id);
			dbHelper.close();
    	} catch (Exception e) {
    		Log.e("getNotificationContent", "Error: "+e.getMessage());
    	}
    	return content;
    }
    
    public String getNotificationTitle(int notification, int type)
    {
    	String title = "";
    	try {
	    	LiveViewDbHelper dbHelper;
	    	Cursor cursor;
			dbHelper = new LiveViewDbHelper(this);	
			dbHelper.openToRead();
			cursor = dbHelper.getAlertsOfType(type);
			int index_TITLE = cursor.getColumnIndex(LiveViewDbConstants.COLUMN_ALERT_ITEMS_TITLE);
			cursor.moveToPosition(notification);
			title = cursor.getString(index_TITLE);
			cursor.close();
			dbHelper.close();
    	} catch (Exception e) {
    		Log.e("getNotificationTitle", "Error: "+e.getMessage());
    	}
    	return title;
    }
    
    public long getNotificationTime(int notification, int type) {
    	long timestamp = 0;
    	try {
	    	LiveViewDbHelper dbHelper;
	    	Cursor cursor;
			dbHelper = new LiveViewDbHelper(this);	
			dbHelper.openToRead();
			cursor = dbHelper.getAlertsOfType(type);
			int index_TIMESTAMP = cursor.getColumnIndex(LiveViewDbConstants.COLUMN_ALERT_ITEMS_TIMESTAMP);
			cursor.moveToPosition(notification);
			timestamp = cursor.getLong(index_TIMESTAMP);
			cursor.close();
			dbHelper.close();
    	} catch (Exception e) {
    		Log.e("getNotificationTime", "Error: "+e.getMessage());
    	}
    	return timestamp;
    }
 
    public int getNotificationUnreadCount(int type)
    {
    	LiveViewDbHelper dbHelper;
    	Cursor cursor;
		dbHelper = new LiveViewDbHelper(this);	
		dbHelper.openToRead();
		cursor = dbHelper.getAlertsOfType(type);
		int index_UNREAD = cursor.getColumnIndex(LiveViewDbConstants.COLUMN_ALERT_ITEMS_UNREAD);
		int unreadCount = 0;
		for(cursor.moveToFirst(); !(cursor.isAfterLast()); cursor.moveToNext()) {
			if (cursor.getInt(index_UNREAD)==1) {
				unreadCount++;
			}
		}
		cursor.close();
		dbHelper.close();
        return unreadCount;
    }
    
    public int getNotificationTotalCount(int type)
    {
    	LiveViewDbHelper dbHelper;
    	Cursor cursor;
		dbHelper = new LiveViewDbHelper(this);	
		dbHelper.openToRead();
		cursor = dbHelper.getAlertsOfType(type);
		int totalCount = cursor.getCount();
		cursor.close();
		dbHelper.close();
        return totalCount;
    }
    
    /* Media receiver */
    
    private boolean MediaInfoNeedsUpdate = false;
    private String MediaInfoArtist = "";
    private String MediaInfoTrack  = "";
    private String MediaInfoAlbum  = "";
    
    public Boolean getMediaInfoNeedsUpdate()
    {
        return MediaInfoNeedsUpdate;
    }

    public void setMediaInfoNeedsUpdate(Boolean NotificationNeedsUpdate)
    {
        this.MediaInfoNeedsUpdate = NotificationNeedsUpdate;
    }
    
    public String getMediaInfoArtist()
    {
        return MediaInfoArtist;
    }
    
    public String getMediaInfoTrack()
    {
        return MediaInfoTrack;
    }
    
    public String getMediaInfoAlbum()
    {
        return MediaInfoAlbum;
    }
    
    private BroadcastReceiver media_receiver = new BroadcastReceiver()
    {
    	@Override
    	public void onReceive(Context context, Intent intent)
    	{
    		//String action = intent.getAction();
    		//String cmd = intent.getStringExtra("command");
    		//Log.d("mIntentReceiver.onReceive ", action + " / " + cmd);
    		MediaInfoArtist = intent.getStringExtra("artist");
    		MediaInfoAlbum = intent.getStringExtra("album");
    		MediaInfoTrack = intent.getStringExtra("track");
    		MediaInfoNeedsUpdate = true;
    		Log.d("OLV Music","Artist: "+MediaInfoArtist+", Album: "+MediaInfoAlbum+" and Track: "+MediaInfoTrack);
    		if (workerThread != null)
    			workerThread.mediaUpdate();
    	}
    };

    public class PluginCommandReceiver extends BroadcastReceiver  
    {  
    	@Override  
    	public void onReceive(Context context, Intent intent)  
    	{  
    		Log.w("PLUGIN DEBUG", "Received intent, current LiveView status is: "+workerThread.getLiveViewStatus());
    		Log.w("PLUGIN DEBUG", "Command: "+intent.getExtras().getString("command"));
    		try
    		{
    			switch (workerThread.getLiveViewStatus()){
    			case MessageConstants.DEVICESTATUS_OFF:
    				if (intent.getExtras().getString("command").contains("vibrate"))
    				{
    					Log.w("PLUGIN DEBUG", "Sent vibration.");
    					int vdelay = intent.getExtras().getInt("delay");
    					int vtime = intent.getExtras().getInt("time");
    					workerThread.sendCall(new SetVibrate(vdelay, vtime));
    				}
    				else
    				{
    					if (intent.getExtras().getString("command").contains("notify"))
    					{
    						Log.w("PLUGIN DEBUG", "Sent vibration & blink.");
    						int vdelay = intent.getExtras().getInt("delay");
    						int vtime = intent.getExtras().getInt("time");
    						workerThread.sendCall(new SetVibrate(vdelay, vtime));
    						workerThread.sendCall(new SetLed(Color.GREEN, vdelay, vtime));
    						if (intent.getExtras().getInt("displaynotification")==1)
    						{
    							String line1 = intent.getExtras().getString("line1");
    							String line2 = intent.getExtras().getString("line2");
    							if (line1==null) line1 = "";
    							if (line2==null) line2 = "";
    							int icon_type = intent.getExtras().getInt("icon_type");
    							/* if (icon_type>2)
   		    					{
   		    						icon_type=2;
   		    					} */
    							byte[] img = intent.getExtras().getByteArray("icon");
    							workerThread.showNewAlert(line1, line2, icon_type, img);
    						}
    					}
    					else
    					{
    						if (intent.getExtras().getString("command").contains("awaken"))
    						{
    							Log.w("PLUGIN DEBUG", "Sent awaken.");
    							workerThread.sendCall(new SetScreenMode((byte) MessageConstants.BRIGHTNESS_MAX));
    							workerThread.openMenuFromStandby();
    						}
    						else
    						{
    							String message = "Error: Plugin command receiver: Unknown command.";
    							Log.e(TAG, message);

    							return;
    						}
    					}
    				}
    				break;
    			case MessageConstants.DEVICESTATUS_ON:
    				if (intent.getExtras().getString("command").contains("vibrate"))
    				{
    					Log.w("PLUGIN DEBUG", "Sent vibration.");
    					int vdelay = intent.getExtras().getInt("delay");
    					int vtime = intent.getExtras().getInt("time");
    					workerThread.sendCall(new SetVibrate(vdelay, vtime));
    				}
    				break;
    			case MessageConstants.DEVICESTATUS_MENU:
    				if (intent.getExtras().getString("command").contains("vibrate"))
    				{
    					Log.w("PLUGIN DEBUG", "Sent vibration.");
    					int vdelay = intent.getExtras().getInt("delay");
    					int vtime = intent.getExtras().getInt("time");
    					workerThread.sendCall(new SetVibrate(vdelay, vtime));
    				}
    				if (intent.getExtras().getString("command").contains("panel"))
    				{
    					Log.w("PLUGIN DEBUG", "Show panel.");
    					String top_string = intent.getExtras().getString("top_string");
    					String bottom_string = intent.getExtras().getString("bottom_string");
    					boolean isAlert = intent.getExtras().getBoolean("isAlert");
    					boolean useImage = intent.getExtras().getBoolean("useImage");
    					byte[] img = intent.getExtras().getByteArray("image");
    					workerThread.showPanel(top_string, bottom_string, isAlert, useImage, img);
    					Log.e("DEBUG", top_string);
    					Log.e("DEBUG", bottom_string);
    				}    		    			
    				break;
    			default:
    				String message = "Error: Unknown device state!";
    				Log.e(TAG, message);

    				break;
    			}
    		}
    		catch (IOException e)
    		{
    			String message = "Error: IOException in plugin command receiver: " + e.getMessage();
    			Log.e(TAG, message);

    			e.printStackTrace();
    			return;
    		}
    	}  
    }  

    public class MyPhoneStateListener extends PhoneStateListener {
    	Context context;
    	@Override
    	public void onCallStateChanged(int state,String incomingNumber){
    		try {
    			if (incomingNumber.length()>0) Log.d("PhoneCallStateNotified", "Incoming number "+incomingNumber);
    			if (state == TelephonyManager.CALL_STATE_RINGING)
    			{
    				if (workerThread.getLiveViewStatus()==MessageConstants.DEVICESTATUS_OFF)
    				{
    					workerThread.showIncomingCallScreen(state, "Incoming call", getContactByAddr(LiveViewService.this, incomingNumber));
    				}
    				Log.d("PhoneCallStateNotified", "Status: RINGING");
    			}
    			if (state == TelephonyManager.CALL_STATE_IDLE)
    			{
    				Log.d("PhoneCallStateNotified", "Status: IDLE");
    			}
    			if (state == TelephonyManager.CALL_STATE_OFFHOOK)
    			{
    				Log.d("PhoneCallStateNotified", "Status: OFFHOOK");
    			} 
    		} catch (Exception e) {
    			String message = "Exception in incoming call receiver: " + e.getMessage();
    			Log.e(TAG, message);
    			//e.printStackTrace();
    		}	
    	}
    }        

    private static String getContactByAddr(Context context, String phoneNumber) {
    	Uri personUri = null;
    	Cursor cur = null;

    	try {
    		personUri = Uri.withAppendedPath(
    				ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
    				phoneNumber);
    		cur = context.getContentResolver()
    				.query(personUri,
    						new String[] { PhoneLookup.DISPLAY_NAME }, null,
    						null, null);
    		if (cur.moveToFirst()) {
    			int nameIdx = cur.getColumnIndex(PhoneLookup.DISPLAY_NAME);
    			return cur.getString(nameIdx);
    		}
    		return phoneNumber;
    	} finally {
    		if (cur != null) {
    			cur.close();
    		}
    	}
    }

    private String PLUGIN_COMMAND = "nl.rnplus.olv.plugin.command";
    private String PLUGIN_EVENT = "nl.rnplus.olv.plugin.event";

    public void sendEvent(String event, int a, int b, String string1, String string2) {
    	Intent bci = new Intent(PLUGIN_EVENT);
    	Bundle bcb = new Bundle();
    	bcb.putString("event", event);
    	bcb.putString("plugin", "Here should be the target plugin package name");
    	bcb.putInt("p1", a);
    	bcb.putInt("p2", b);
    	if (string1==null) string1="no value";
    	if (string2==null) string2="no value";
    	bcb.putString("p3", (String) string1.toString());
    	bcb.putString("p4", (String) string2.toString());
    	long time = System.currentTimeMillis();
    	bcb.putLong("timestamp", time);
    	bci.putExtras(bcb);
    	sendBroadcast(bci);
    }
}
